using System;
using System.Diagnostics;
using System.Text;
using System.Data;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Runtime.InteropServices;
using MSHelpLib.BaseCode;
namespace MSHelpLib.CHM.Decoding
{
    /// <summary>
    /// Summary description for CHMFile.
    /// </summary>
	public class CHMStream : IDisposable, IHelpStream
	{
        /// <summary>
        /// Initializes a new instance of the <see cref="CHMStream"/> class.
        /// </summary>
		public CHMStream()
		{
		}
		
		#region CHMStream Enums
		// the two available spaces in a CHM file                      
		// N.B.: The format supports arbitrarily many spaces, but only 
		//       two appear to be used at present.                     
        /// <summary>
        /// 
        /// </summary>
		public enum CHM_COMPRESSION {
            /// <summary>
            /// 
            /// </summary>
            CHM_UNCOMPRESSED=0,
            /// <summary>
            /// 
            /// </summary>
            CHM_COMPRESSED=1};

		// resolve a particular object from the archive 
        /// <summary>
        /// 
        /// </summary>
		public enum CHM_RESOLVE {
            /// <summary>
            /// 
            /// </summary>
            CHM_RESOLVE_SUCCESS=0,
            /// <summary>
            /// 
            /// </summary>
            CHM_RESOLVE_FAILURE=1};
														  
		// retrieve part of an object from the archive 
        /// <summary>
        /// 
        /// </summary>
		public enum CHM_ENUMERATE
		{
            /// <summary>
            /// 
            /// </summary>
			None=0,
            /// <summary>
            /// 
            /// </summary>
			CHM_ENUMERATE_NORMAL	=1,
            /// <summary>
            /// 
            /// </summary>
			CHM_ENUMERATE_META		=2,
            /// <summary>
            /// 
            /// </summary>
			CHM_ENUMERATE_SPECIAL	=4,
            /// <summary>
            /// 
            /// </summary>
			CHM_ENUMERATE_FILES		=8,
            /// <summary>
            /// 
            /// </summary>
			CHM_ENUMERATE_DIRS		=16,
            /// <summary>
            /// 
            /// </summary>
			CHM_ENUMERATE_ALL		=31};

        /// <summary>
        /// 
        /// </summary>
		public enum CHM_ENUMERATOR
		{
            /// <summary>
            /// 
            /// </summary>
			CHM_ENUMERATOR_FAILURE	=0,
            /// <summary>
            /// 
            /// </summary>
			CHM_ENUMERATOR_SUCCESS	=2,
            /// <summary>
            /// 
            /// </summary>
			CHM_ENUMERATOR_CONTINUE =1
		};
		#endregion

		#region Internal Parameters
		private int ffs(int val)
		{
			int bit=1;
			int idx=1;
			while (bit != 0  &&  (val & bit) == 0)
			{
				bit <<= 1;
				++idx;
			}
			if (bit == 0)
				return 0;
			else
				return idx;
		}

		// names of sections essential to decompression 
		private const string _CHMU_RESET_TABLE = @"::DataSpace/Storage/MSCompressed/Transform/{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable";
		private const string _CHMU_LZXC_CONTROLDATA = @"::DataSpace/Storage/MSCompressed/ControlData";
		private const string _CHMU_CONTENT = @"::DataSpace/Storage/MSCompressed/Content";
		private const string _CHMU_SPANINFO =	@"::DataSpace/Storage/MSCompressed/SpanInfo";

	    private UInt64              dir_offset=0;
		private UInt64              dir_len=0;    
		private UInt64              data_offset=0;
		private Int32               index_root=0;
		private Int32               index_head=0;
		private UInt32              block_len=0;     		
		
		private IFileInfo			rt_unit;
		private IFileInfo			cn_unit;
		private chmLzxcResetTable	reset_table;
		private bool				compression_enabled=false;

		// LZX control data 
		private int					window_size=0;
		private UInt32              reset_interval=0;
		private UInt32              reset_blkcount=0;
		private BinaryReaderEx		st=null;

		// decompressor state 
		private lzw					lzx_state;		
		private int                 lzx_last_block=0;
		private IHelpFile			m_helpFile=null;
		#endregion


        /// <summary>
        /// Gets or sets the help file.
        /// </summary>
        /// <value>The help file.</value>
		public IHelpFile HelpFile
		{
			get { return m_helpFile;}
			set { m_helpFile=value;}
		}

		#region CheckSig
		private bool CheckSig(string Sig1, char[] Sig2)
		{
			int  i=0;
			foreach(char ch in Sig1.ToCharArray())
			{
				if(ch!=Sig2[i])
					return false;
				i++;
			}
			return true;	
		}

		#endregion
		
		#region Extract File from CHM Stream

		//	* utility methods for dealing with compressed data		 
		// get the bounds of a compressed block.  return 0 on failure 			
		private int _chm_get_cmpblock_bounds(System.IO.BinaryReader st, UInt64 block, ref UInt64 start, ref UInt64 len)
		{
			// for all but the last block, use the reset table 
			if (block < reset_table.block_count-1)
			{
				// unpack the start address 				
				st.BaseStream.Seek((long)data_offset + (long)rt_unit.Offset + (long)reset_table.table_offset + (long)(block*8),SeekOrigin.Begin);
				start=st.ReadUInt64();
				len=st.ReadUInt64();
			}

			// for the last block, use the span in addition to the reset table 
			else
			{				
				// unpack the start address 
				st.BaseStream.Seek((long)data_offset + (long)rt_unit.Offset + (long)reset_table.table_offset + (long)(block*8),SeekOrigin.Begin);
				start=st.ReadUInt64();
				len = reset_table.compressed_len;
			}

			// compute the length and absolute start address 
			len -= start;
			start += data_offset + cn_unit.Offset;

			return 1;
		}

		// decompress the block.  must have lzx_mutex. 
		private ulong _chm_decompress_block(UInt64 block, System.IO.Stream OutBuffer)
		{
			// byte []cbuffer = new byte(reset_table.block_len + 6144);
			ulong cmpStart=0;                                    // compressed start  
			ulong cmpLen=0;                                       // compressed len    
			UInt32 blockAlign = (UInt32)(block % reset_blkcount); // reset intvl. aln. 			
			
			// check if we need previous blocks 
			if (blockAlign != 0)			
			{
				/* fetch all required previous blocks since last reset */
				for (UInt32 i = blockAlign; i > 0; i--)
				{
					UInt32 curBlockIdx = (UInt32)(block-i);
					
					/* check if we most recently decompressed the previous block */
					if ((ulong)lzx_last_block != curBlockIdx)
					{
						if ((curBlockIdx % reset_blkcount)==0)
						{
							lzx_state.LZXreset();
						}
						
						_chm_get_cmpblock_bounds(st,curBlockIdx, ref cmpStart, ref cmpLen);
						st.BaseStream.Seek((long)cmpStart,SeekOrigin.Begin);						
						if (lzx_state.LZXdecompress(st,OutBuffer, ref cmpLen, ref reset_table.block_len) != lzw.DECR_OK)
							return (Int64)0;
					}
					lzx_last_block = (int)(curBlockIdx);
				}
			}
			else
			{
				if ((block % reset_blkcount)==0)
				{
					lzx_state.LZXreset();
				}		
			}

			// decompress the block we actually want 
			if (_chm_get_cmpblock_bounds(st, block, ref cmpStart, ref cmpLen)==0)
				return 0;

			st.BaseStream.Seek((long)cmpStart,SeekOrigin.Begin);
							
			if (lzx_state.LZXdecompress(st, OutBuffer, ref cmpLen,ref reset_table.block_len) != lzw.DECR_OK)
				return (Int64)0;

			lzx_last_block = (int)block;

			// XXX: modify LZX routines to return the length of the data they
			// * decompressed and return that instead, for an extra sanity check.
		     return reset_table.block_len;
		}

		// grab a region from a compressed block 
		private ulong _chm_decompress_region(Stream buf, ulong  start, ulong len)
		{
			ulong nBlock, nOffset;
			ulong nLen;
			ulong gotLen;
			// byte [] ubuffer=null;

			if (len <= 0)
				return (Int64)0;

			// figure out what we need to read 
			nBlock = start / reset_table.block_len;
			nOffset = start % reset_table.block_len;
			nLen = len;
			if (nLen > (reset_table.block_len - nOffset))
				nLen = reset_table.block_len - nOffset;

			// data request not satisfied, so... start up the decompressor machine 			
			if (lzx_state==null)
			{
				int window_size = ffs(this.window_size) - 1;
				lzx_last_block = -1;

				lzx_state=new lzw();
				lzx_state.LZXinit(window_size);
			}

			// decompress some data 
			MemoryStream ms=new MemoryStream((int)reset_table.block_len+6144);			
			gotLen = _chm_decompress_block(nBlock, ms);
			if (gotLen < nLen)
				nLen = gotLen;	
			
			// memcpy(buf, ubuffer+nOffset, (unsigned int)nLen);
			ms.Position=(long)nOffset;
			buf.Write(ms.ToArray(),(int)nOffset,(int)nLen);

			// for(ulong i=0;i<nLen;i++) buf.WriteByte((byte)ms.ReadByte());

			buf.Flush();
			return nLen;
		}

		// retrieve (part of) an object 
		private ulong chm_retrieve_object(IFileInfo ui, Stream buf, ulong addr, ulong len)
		{
			// starting address must be in correct range 
			if (addr < 0  ||  addr >= (ulong)ui.Length)
				return (Int64)0;

			// clip length 
			if (addr + (ulong)len > (ulong)ui.Length)
				len = (ulong)ui.Length - (ulong)addr;

			// if the file is uncompressed, it's simple 
			if (ui.Stream == (ushort)CHMStream.CHM_COMPRESSION.CHM_UNCOMPRESSED)
			{
				// read data 
				long FilePos=st.BaseStream.Position;				
				st.BaseStream.Seek((long)((long)data_offset + (long)ui.Offset + (long)addr),SeekOrigin.Begin);
				// byte [] buffer=st.ReadBytes((int)len);
				buf.Write(st.ReadBytes((int)len),0,(int) len);
				st.BaseStream.Seek(FilePos,SeekOrigin.Begin);
				return (ulong)len;
			}

			// else if the file is compressed, it's a little trickier 
			else // ui->space == CHM_COMPRESSED 
			{			
				if (lzx_state!=null)
				{
					lzx_state.LZXteardown();
					lzx_state=null;
				}
				ulong swath=0, total=0;
				do 
				{
					if (!compression_enabled)
						return total;

					// swill another mouthful 
					swath = _chm_decompress_region(buf, ui.Offset + addr, len);

					// if we didn't get any... 
					if (swath == 0)
					{
						Trace.Assert((total!=ui.Length),"De-compress failed","Length Required = "+ui.Length.ToString()+" Length returned = "+total.ToString());
						return total;
					}

					// update stats 
					total += swath;
					len -= swath;
					addr += swath;
				} while (len != 0);		
				lzx_state=null;

				Trace.Assert((len!=ui.Length),"De-compress failed","Length Required = "+ui.Length.ToString()+" Length returned = "+len.ToString());
				return len;
			}
		}
		#endregion
	
		#region Enumerate functions
		// Enumerate the objects in the .chm archive 
		// Use delegate to handle callback

        /// <summary>
        /// 
        /// </summary>
		public delegate void CHMFileFound(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status);
        /// <summary>
        /// Occurs when [CHM file found event].
        /// </summary>
		public event CHMFileFound CHMFileFoundEvent;

        /// <summary>
        /// Called when [file found].
        /// </summary>
        /// <param name="Info">The info.</param>
        /// <param name="Status">The status.</param>
		public void OnFileFound(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status)
		{
			if (CHMFileFoundEvent!=null)
				CHMFileFoundEvent(Info,ref Status);
		}

		private int chm_enumerate(CHM_ENUMERATE what)
		{
			Int32 curPage;

			// buffer to hold whatever page we're looking at 			
			chmPmglHeader header;
			uint end=0;
			uint cur=0;			

			// the current ui 
			chmUnitInfo ui= new chmUnitInfo();
			CHMStream.CHM_ENUMERATE flag=CHMStream.CHM_ENUMERATE.None;

			// starting page 
			curPage = index_head;

			// until we have either returned or given up 
			while (curPage != -1)
			{
				st.BaseStream.Seek((long)((long)dir_offset + (long)(curPage*block_len)),SeekOrigin.Begin);

				// figure out start and end for this page 
				cur = (uint)st.BaseStream.Position;
				
				header=new chmPmglHeader();
				if (header.Read_pmgl_header(st)==0)				
					return 0;

				end = (uint)(st.BaseStream.Position + block_len - (header.free_space)- chmPmglHeader._CHM_PMGL_LEN);

				// loop over this page 
				while (st.BaseStream.Position < end)
				{
					if (header._chm_parse_PMGL_entry(st,ref ui)==0)
						return 0;

					// check for DIRS 
					if (ui.length == 0  &&  ((what & CHM_ENUMERATE.CHM_ENUMERATE_DIRS)==0))
						continue;

					// check for FILES 
					if (ui.length != 0  &&  ((what & CHM_ENUMERATE.CHM_ENUMERATE_FILES)==0))
						continue;

					// check for NORMAL vs. META 
					if (ui.path[0] == '/')
					{
						// check for NORMAL vs. SPECIAL 
						if (ui.path.Length>2)
						{
							if (ui.path[1] == '#'  ||  ui.path[1] == '$')
								flag = CHMStream.CHM_ENUMERATE.CHM_ENUMERATE_SPECIAL;
							else
								flag = CHMStream.CHM_ENUMERATE.CHM_ENUMERATE_NORMAL;
						}					
						else
							flag = CHMStream.CHM_ENUMERATE.CHM_ENUMERATE_META;
						if ((what & flag)==0)
							continue;
					}

					// call the enumerator 
					{
						CHMStream.CHM_ENUMERATOR status = CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE;
						OnFileFound(ui,ref status);
													
						switch (status)
						{
							case CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_FAILURE:  
								return 0;

							case CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE: 
								break;

							case CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_SUCCESS:  
								return 1;

							default:
								break;
						}
					}
				}

				// advance to next page 
				curPage = header.block_next;
			}

			return 1;
		}
		#endregion
		
		#region IDisposable Members

		private bool disposed=false;
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
		public void Dispose()
		{
			Dispose(true);
			// This object will be cleaned up by the Dispose method.
			// Therefore, you should call GC.SupressFinalize to
			// take this object off the finalization queue 
			// and prevent finalization code for this object
			// from executing a second time.
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Dispose(bool disposing) executes in two distinct scenarios. 
		/// If disposing equals true, the method has been called directly 
		/// or indirectly by a user's code. Managed and unmanaged resources 
		/// can be disposed. 
		/// If disposing equals false, the method has been called by the 
		/// runtime from inside the finalizer and you should not reference  
		/// other objects. Only unmanaged resources can be disposed.
		/// </summary>
		/// <param name="disposing">disposing flag</param>
		private void Dispose(bool disposing)
		{
			// Check to see if Dispose has already been called.
			if(!this.disposed)
			{
				// If disposing equals true, dispose all managed 
				// and unmanaged resources.
				if(disposing)
				{
					// Dispose managed resources.					
				}
			}
			disposed = true;         
		}

		#endregion

		#region chm_resolve_object (LowLevel Func)
		// resolve a particular object from the archive 
		private CHMStream.CHM_RESOLVE chm_resolve_object(string objPath, ref IFileInfo ui)
		{     			
			Int32 curPage;
	
			// starting page 
			curPage = index_root;

			// until we have either returned or given up 
			while (curPage != -1)
			{
				st.BaseStream.Seek((long)((long)dir_offset + (long)(curPage*block_len)),SeekOrigin.Begin);
				
				char[] sig=st.ReadChars(4);
				st.BaseStream.Seek(-4,SeekOrigin.Current);
				if (CheckSig("PMGL",sig))
				{
					chmPmglHeader PmglHeader=new chmPmglHeader();
					if (PmglHeader.Read_pmgl_header(st)==1)
					{
						// scan block 
						ui=PmglHeader.FindObject(st,block_len,objPath);
						if (ui== null) 
							return CHMStream.CHM_RESOLVE.CHM_RESOLVE_FAILURE;

						// parse entry and return 
						return CHMStream.CHM_RESOLVE.CHM_RESOLVE_SUCCESS;
					}
				}	
				else if (CheckSig("PMGI",sig))
				{			
					chmPmgiHeader pmgiHeader=new chmPmgiHeader();
					pmgiHeader.Read_pmgi_header(st);
					curPage = pmgiHeader._chm_find_in_PMGI(st, block_len, objPath);
				}				
				else
					// else, we are confused.  give up. 
					return CHMStream.CHM_RESOLVE.CHM_RESOLVE_FAILURE;
			}

			// didn't find anything.  fail. 
			return CHMStream.CHM_RESOLVE.CHM_RESOLVE_FAILURE;
		}
		#endregion

		private string m_FileName="";
        /// <summary>
        /// Gets the name of the file.
        /// </summary>
        /// <value>The name of the file.</value>
		public string FileName
		{
			get { return m_FileName;}
		}

		// IHelpStream Members		
		#region Open		
        /// <summary>
        /// Opens the specified file name.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <param name="_helpFile">The _help file.</param>
        /// <returns></returns>
		public bool Open(string FileName, IHelpFile _helpFile)
		{
			HelpFile=_helpFile;
			chmItsfHeader        itsfHeader=new chmItsfHeader();
			chmItspHeader        itspHeader=new chmItspHeader();			
			IFileInfo			 uiLzxc=new chmUnitInfo();
			chmLzxcControlData   ctlData=new chmLzxcControlData();
						
			if (!File.Exists(FileName))
				return false;

			m_FileName=FileName;

			st=new BinaryReaderEx(File.Open(FileName,FileMode.Open,FileAccess.Read,FileShare.Read));
			if (st==null)
				return false;			

			// read and verify header 			
			if (itsfHeader.Read_itsf_header(st)==0)
			{
				st.Close();
				return false;
			}
			st.BaseStream.Seek((long)itsfHeader.dir_offset,SeekOrigin.Begin);			

			// stash important values from header 
			dir_offset  = itsfHeader.dir_offset;
			dir_len     = itsfHeader.dir_len;
			data_offset = itsfHeader.data_offset;

			// now, read and verify the directory header chunk 
			if (itspHeader.Read_itsp_header(st)==0)
			{
				st.Close();
				return false;
			}

			// grab essential information from ITSP header 
			dir_offset += (UInt64)itspHeader.header_len;
			dir_len    -= (UInt64)itspHeader.header_len;
			index_root  = itspHeader.index_root;
			index_head  = itspHeader.index_head;
			block_len   = itspHeader.block_len;

			// if the index root is -1, this means we don't have any PMGI blocks.
			// as a result, we must use the sole PMGL block as the index root
	     
			if (index_root == -1)
				index_root = index_head;

			compression_enabled=true;

			// prefetch most commonly needed unit infos 			
			if (CHM_RESOLVE.CHM_RESOLVE_SUCCESS != chm_resolve_object(_CHMU_RESET_TABLE, ref rt_unit)    
				|| rt_unit.Stream== (ushort)CHM_COMPRESSION.CHM_COMPRESSED                        
				|| CHM_RESOLVE.CHM_RESOLVE_SUCCESS != chm_resolve_object(_CHMU_CONTENT,ref cn_unit)    
				|| cn_unit.Stream== (ushort)CHM_COMPRESSION.CHM_COMPRESSED                        
				|| CHM_RESOLVE.CHM_RESOLVE_SUCCESS != chm_resolve_object(_CHMU_LZXC_CONTROLDATA, ref uiLzxc)                
				|| uiLzxc.Stream == (ushort)CHM_COMPRESSION.CHM_COMPRESSED)
			{
				compression_enabled=false;
				// st.Close();				
				// return false;
			}

			// try to read span 
			// N.B.: we've already checked that uiSpan is in the uncompressed section,
			//        so this should not require attempting to decompress, which may
			//        rely on having a valid "span"		     
			if (compression_enabled)
			{		
				reset_table=new chmLzxcResetTable();
				st.BaseStream.Seek((long)((long)data_offset + (long)rt_unit.Offset),SeekOrigin.Begin);
				if (reset_table.Read_lzxc_reset_table(st)!=1)
					compression_enabled=false;
			}

			if (compression_enabled)
			{			
				// read control data 
				ctlData=new chmLzxcControlData();
				st.BaseStream.Seek((long)((long)data_offset + (long)uiLzxc.Offset),SeekOrigin.Begin);
				if (ctlData.Read_lzxc_control_data(st)!=1)
				{
					compression_enabled=false;
				}			

				window_size = (int)ctlData.windowSize;
				reset_interval = ctlData.resetInterval;
				try
				{
					reset_blkcount = (uint)(reset_interval / (window_size / 2) * ctlData.windowsPerReset);
				}
				catch(Exception)
				{
					reset_blkcount=0;
				}
			}		
			return true;
		}
		#endregion

		#region Close 
        /// <summary>
        /// Closes this instance.
        /// </summary>
		public void Close()
		{	
			if (lzx_state!=null)	
				lzx_state.LZXteardown();	
			lzx_state=null;

			if (st!=null)
				st.Close();
			st=null;			
		}
		#endregion

		#region	Extract File
        /// <summary>
        /// Extracts the specified file info.
        /// </summary>
        /// <param name="fileInfo">The file info.</param>
        /// <returns></returns>
		public MemoryStream Extract(IFileInfo fileInfo)
		{
			MemoryStream st=new MemoryStream((int)fileInfo.Length);
			ulong len=chm_retrieve_object(fileInfo,st,0,fileInfo.Length);
			return st;
		}

        /// <summary>
        /// Extracts the specified file name.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <returns></returns>
		public MemoryStream Extract(string FileName)
		{
			IFileInfo fileInfo=FindFile(FileName);			
			if (fileInfo==null)
				return null;
			return Extract(fileInfo);
		}

		#endregion

		#region FileCount
		private int FileCount=-1;

		private PAFEnum FileCountProc(IFileInfo fileInfo)
		{
			FileCount++;
			return PAFEnum.Continue;
		}

        /// <summary>
        /// Gets the number of files.
        /// </summary>
        /// <value>The number of files.</value>
		public int NumberOfFiles
		{
			get 
			{ 
				if (FileCount==-1)
				{
					FileCount=0;
					this.ProcessAllFiles(new MSHelpLib.ProcessAllFilesDelegate(FileCountProc));
				}
				return FileCount; 
			}
		}
		#endregion

		#region ProcessAllFiles
        /// <summary>
        /// Processes all files.
        /// </summary>
        /// <param name="processAllFiles">The process all files.</param>
        /// <returns></returns>
		public int ProcessAllFiles(MSHelpLib.ProcessAllFilesDelegate processAllFiles)
		{
			Int32 curPage;

			// buffer to hold whatever page we're looking at 			
			chmPmglHeader header;
			uint end=0;
			uint cur=0;			

			// the current ui 
			// chmUnitInfo ui= new chmUnitInfo();			

			// starting page 
			curPage = index_head;

			if (st==null)
				return 1;

			// until we have either returned or given up 
			while (curPage != -1)
			{
				st.BaseStream.Seek((long)((long)dir_offset + (long)(curPage*block_len)),SeekOrigin.Begin);

				// figure out start and end for this page 
				cur = (uint)st.BaseStream.Position;
				
				header=new chmPmglHeader();
				if (header.Read_pmgl_header(st)==0)				
					return 0;

				end = (uint)(st.BaseStream.Position + block_len - (header.free_space)- chmPmglHeader._CHM_PMGL_LEN);

				// loop over this page 
				while (st.BaseStream.Position < end)
				{
					chmUnitInfo fileInfo=new chmUnitInfo();
					if (header._chm_parse_PMGL_entry(st,ref fileInfo)==0)
						return 0;
					fileInfo.HelpStream=this;

					// Save File Pos
					long fp=st.BaseStream.Position;

					// Call ProcessAllFiles CallBack
					PAFEnum rc=processAllFiles(fileInfo);

					// Restore File Pos
					st.BaseStream.Seek(fp,SeekOrigin.Begin);
									
					// Handle Call back return code				
					switch(rc)
					{
						case PAFEnum.Abort:  
							return 0;

						case PAFEnum.Continue: 
							break;
						
						default:
							break;
					}
				}

				// advance to next page 
				curPage = header.block_next;
			}
			return 1;
		}
		#endregion

		#region FindFileByExtension
		private string FindFIleByExt="";
		private MSHelpLib.IFileInfo InfoFileByExt=null;
		private PAFEnum FindFileByExtension(IFileInfo fileInfo)
		{
			if (Path.GetExtension(fileInfo.FileName).ToLower()==FindFIleByExt)
			{
				InfoFileByExt=fileInfo;
				return PAFEnum.Abort;
			}
			else
				return PAFEnum.Continue;
		}

        /// <summary>
        /// Finds the file by ext.
        /// </summary>
        /// <param name="Ext">The ext.</param>
        /// <returns></returns>
		public MSHelpLib.IFileInfo FindFileByExt(string Ext)
		{
			if (!Ext.StartsWith("."))
				return null;

			FindFIleByExt=Ext.ToLower();
			InfoFileByExt=null;
			ProcessAllFiles(new MSHelpLib.ProcessAllFilesDelegate(FindFileByExtension));
			return InfoFileByExt;
		}
		#endregion

		#region FindFile

        private IFileInfo FindFileProcResult=null;
        private string FindFileName="";
        private PAFEnum FindFileProc(IFileInfo fileInfo)
		{
            BaseStructure x = new BaseStructure();
            string FoundName = x.NormallizeFileName(fileInfo.FileName).ToLower();
            if (FoundName.IndexOf(FindFileName)>-1)
            {
                FindFileProcResult=fileInfo;
                return PAFEnum.Abort;
            }
            else    
                return PAFEnum.Continue;
		}

        /// <summary>
        /// Finds the file.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <returns></returns>
		public IFileInfo FindFile(string FileName)
		{            
            BaseStructure x=new BaseStructure();
            FindFileName = x.NormallizeFileName(FileName.ToLower());

            FindFileProcResult=null;
            this.ProcessAllFiles(new MSHelpLib.ProcessAllFilesDelegate(FindFileProc));
            return FindFileProcResult;

            //if (st==null)
            //    return null;

            //BaseStructure x=new BaseStructure();
            //string m_FileFind=x.NormallizeFileName(FileName);

            //Int32 curPage;

            //// starting page 
            //curPage = index_root;

            //// until we have either returned or given up 
            //long Chunk=-1;
            //bool bChunkSet=false;
            //int BlockCount=-1;
            //bool bFirst=true;
            //while (curPage != -1)
            //{				
            //    st.BaseStream.Seek((long)((long)dir_offset + (long)(curPage*block_len)),SeekOrigin.Begin);
			
            //    curPage++;
            //    char[] sig=st.ReadChars(4);
            //    st.BaseStream.Seek(-4,SeekOrigin.Current);

            //    if (CheckSig("PMGL",sig))
            //    {
            //        if (bFirst)
            //        {
            //            // Must only be one directory chunk
            //            Chunk=0;
            //            bChunkSet=true;
            //            break;
            //        }
            //        bFirst=false;
            //        continue;				
            //    }
            //    if (CheckSig("PMGI",sig))
            //    {			
            //        chmPmgiHeader pmgiHeader=new chmPmgiHeader();
            //        pmgiHeader.Read_pmgi_header(st);

            //        long fp=st.BaseStream.Position;															
            //        int FileNameLen=m_FileFind.Length;
					
            //        long end=fp+block_len;
            //        do
            //        {
            //            BlockCount++;
            //            chmPmgiEntry PmgiEntry=new chmPmgiEntry();
            //            PmgiEntry.Read_pmgi_entry(st);
            //            if (PmgiEntry.FileName=="")
            //                break;

            //            string ExtractFileName=pmgiHeader.NormallizeFileName(PmgiEntry.FileName);
            //            int y=ExtractFileName.CompareTo(m_FileFind);
					
            //            if (y>=0)
            //            {
            //                if (BlockCount>0)
            //                {
            //                    if (y==0)
            //                        Chunk=(long)PmgiEntry.DirectoryChunk;	
            //                    else
            //                        Chunk=(long)PmgiEntry.DirectoryChunk-1;	
            //                }
            //                else
            //                    Chunk=0;

            //                bChunkSet=true;
            //                curPage=-1;
            //                break;						
            //            }					
            //        }
            //        while (st.BaseStream.Position<end);
            //    }
            //    else
            //    {
            //        curPage=-1;
            //    }
            //    if (bChunkSet)
            //    {
            //        curPage=-1;
            //        break;
            //    }
            //}			
            //if (Chunk==-1)
            //{
            //    Chunk=BlockCount-1;
            //}

            //if (Chunk>-1)
            //{
            //    st.BaseStream.Seek((long)((long)dir_offset + (long)(Chunk*block_len)),SeekOrigin.Begin);
			
            //    char[] sig=st.ReadChars(4);
            //    st.BaseStream.Seek(-4,SeekOrigin.Current);
            //    if (CheckSig("PMGL",sig))
            //    {
            //        chmPmglHeader PmglHeader=new chmPmglHeader();
            //        if (PmglHeader.Read_pmgl_header(st)==1)
            //        {
            //            // scan block 
            //            IFileInfo ui=PmglHeader.FindObject(st,block_len,m_FileFind);
            //            if (ui== null) 
            //                return null;
            //            ui.HelpStream=this;
            //            return ui;
            //        }
            //    }
            //    else if (CheckSig("PMGI",sig))
            //    {
            //        // Skip This block
            //    }				
            //}

            //// didn't find anything.  fail. 
            //return null;
		}
		#endregion
	}

	#region Structures used by CHM Storage
    /// <summary>
    /// 
    /// </summary>
	public class chmUnitInfo : IFileInfo
	{
        /// <summary>
        /// 
        /// </summary>
		public IHelpStream	m_HelpStream=null;
        /// <summary>
        /// 
        /// </summary>
		public UInt64		start=0;
        /// <summary>
        /// 
        /// </summary>
		public UInt64		length=0;
        /// <summary>
        /// 
        /// </summary>
		public CHMStream.CHM_COMPRESSION space=CHMStream.CHM_COMPRESSION.CHM_UNCOMPRESSED;
        /// <summary>
        /// 
        /// </summary>
		public string		path="";

        /// <summary>
        /// Gets the URL.
        /// </summary>
        /// <value>The URL.</value>
		public string URL
		{
			get
			{
				if (this.HelpStream==null)
					return this.FileName;
				if (this.HelpStream.HelpFile==null)
					return this.FileName;

				string sURL=this.HelpStream.HelpFile.UrlPrefix+this.HelpStream.FileName+"::"+this.FileName;
				return sURL;
			}
		}

        /// <summary>
        /// Gets or sets the help stream.
        /// </summary>
        /// <value>The help stream.</value>
		public IHelpStream HelpStream 
		{ 
			get { return m_HelpStream; } 
			set { m_HelpStream=value;  }
		}
        /// <summary>
        /// Gets the offset.
        /// </summary>
        /// <value>The offset.</value>
		public UInt64 Offset	{ get { return start;} }
        /// <summary>
        /// Gets the length.
        /// </summary>
        /// <value>The length.</value>
		public UInt64 Length	{ get { return length;} }
        /// <summary>
        /// Gets the stream.
        /// </summary>
        /// <value>The stream.</value>
		public UInt16 Stream	{ get { return (UInt16)space;} }
        /// <summary>
        /// Gets the name of the file.
        /// </summary>
        /// <value>The name of the file.</value>
		public string FileName	{ get { return path;} }		
	}
	
	// structure of ITSF headers 						
	internal class chmItsfHeader : BaseStructure
	{
		public const int _CHM_ITSF_V2_LEN=0x58;
		public const int _CHM_ITSF_V3_LEN=0x60;

		public char[]	signature=null;				//  0 (ITSF) 
		public Int32	version=0;					//  4 
		public Int32	header_len=0;				//  8 
		public Int32	unknown_000c=0;				//  c 
		public UInt32	last_modified=0;			// 10 
		public UInt32	lang_id=0;					// 14 			
		public Guid		dir_uuid;					// 18 
		public Guid		stream_uuid;				// 28 
		public UInt64	unknown_offset=0;			// 38 
		public UInt64	unknown_len=0;				// 40 
		public UInt64	dir_offset=0;				// 48 
		public UInt64	dir_len=0;					// 50 
		public UInt64	data_offset=0;				// 58 (Not present before V3) 

		public int Read_itsf_header(BinaryReader st)
		{							
			signature=st.ReadChars(4);
			if (CheckSig("ITSF",signature)==false)
				return 0;
			
			version=st.ReadInt32();
			if (version==4)
				version=3;
			header_len=st.ReadInt32();
			unknown_000c=st.ReadInt32();
			last_modified=st.ReadUInt32();
			lang_id=st.ReadUInt32();
			dir_uuid=new Guid(st.ReadBytes(16));
			stream_uuid=new Guid(st.ReadBytes(16));			
			unknown_offset=st.ReadUInt64();
			unknown_len=st.ReadUInt64();
			dir_offset=st.ReadUInt64();
			dir_len=st.ReadUInt64();

			if (version==2)
			{
				if (header_len != chmItsfHeader._CHM_ITSF_V2_LEN)
					return 0;
			}
			else if (version==3)
			{
				if (header_len != chmItsfHeader._CHM_ITSF_V3_LEN)
					return 0;
			}
			else return 0;

			if (version==3)
				data_offset=st.ReadUInt64();
			else
				data_offset = dir_offset + dir_len;

			return 1;
		}
	}

	// structure of ITSP headers 
    /// <summary>
    /// 
    /// </summary>
	internal class chmItspHeader : BaseStructure
	{
		const int CHM_ITSP_V1_LEN=0x54;

        /// <summary>
        /// 
        /// </summary>
		public char[]	signature=null;		//  0 (ITSP) 
        /// <summary>
        /// 
        /// </summary>
		public Int32    version=0;
        /// <summary>
        /// 
        /// </summary>
		public Int32    header_len=0;
        /// <summary>
        /// 
        /// </summary>
		public Int32    unknown_000c=0;
        /// <summary>
        /// 
        /// </summary>
		public UInt32   block_len=0;
        /// <summary>
        /// 
        /// </summary>
		public Int32    blockidx_intvl=0;
        /// <summary>
        /// 
        /// </summary>
		public Int32    index_depth=0;
        /// <summary>
        /// 
        /// </summary>
		public Int32    index_root=0;
        /// <summary>
        /// 
        /// </summary>
		public Int32    index_head=0;
        /// <summary>
        /// 
        /// </summary>
		public Int32    unknown_0024=0;
        /// <summary>
        /// 
        /// </summary>
		public Int32    num_blocks=0;
        /// <summary>
        /// 
        /// </summary>
		public Int32    unknown_002c=0;
        /// <summary>
        /// 
        /// </summary>
		public UInt32	lang_id=0;
        /// <summary>
        /// 
        /// </summary>
		public Guid		system_uuid;
        /// <summary>
        /// 
        /// </summary>
		public Guid		unknown_0044;

        /// <summary>
        /// Read_itsp_headers the specified st.
        /// </summary>
        /// <param name="st">The st.</param>
        /// <returns></returns>
		public int Read_itsp_header(BinaryReader st)
		{							
			signature=st.ReadChars(4);		//  0 (ITSP) 			
			if (CheckSig("ITSP",signature)==false)
				return 0;

			version=st.ReadInt32();
			header_len=st.ReadInt32();
		
			if (header_len!=CHM_ITSP_V1_LEN)
				return 0;

			unknown_000c=st.ReadInt32();
			block_len=st.ReadUInt32();
			blockidx_intvl=st.ReadInt32();
			index_depth=st.ReadInt32();
			index_root=st.ReadInt32();			
			index_head=st.ReadInt32();				
			unknown_0024=st.ReadInt32();
			num_blocks=st.ReadInt32();
			unknown_002c=st.ReadInt32();
			lang_id=st.ReadUInt32();
			system_uuid=new Guid(st.ReadBytes(16));
			unknown_0044=new Guid(st.ReadBytes(16));

			return 1;
		}
	}

    /// <summary>
    /// 
    /// </summary>
	public class chmPmglHeader : BaseStructure
	{
        /// <summary>
        /// 
        /// </summary>
		public const int _CHM_PMGL_LEN=0x14;
        /// <summary>
        /// 
        /// </summary>
		public char[]	signature=null;           //  0 (PMGL) 
        /// <summary>
        /// 
        /// </summary>
		public UInt32	free_space=0;             //  4 
        /// <summary>
        /// 
        /// </summary>
		public UInt32	unknown_0008=0;           //  8 
        /// <summary>
        /// 
        /// </summary>
		public Int32	block_prev=0;             //  c 
        /// <summary>
        /// 
        /// </summary>
		public Int32	block_next=0;             // 10 

        /// <summary>
        /// Read_pmgl_headers the specified st.
        /// </summary>
        /// <param name="st">The st.</param>
        /// <returns></returns>
		public int Read_pmgl_header(BinaryReader st)
		{
			signature=st.ReadChars(4);
			if (CheckSig("PMGL",signature)==false)
				return 0;

			free_space=st.ReadUInt32();
			unknown_0008=st.ReadUInt32();
			block_prev=st.ReadInt32();
			block_next=st.ReadInt32();
			return 1;
		}

		// parse a PMGL entry into a chmUnitInfo struct; return 1 on success. 
        /// <summary>
        /// _chm_parse_s the PMG l_entry.
        /// </summary>
        /// <param name="st">The st.</param>
        /// <param name="ui">The UI.</param>
        /// <returns></returns>
		public int _chm_parse_PMGL_entry(BinaryReader st, ref chmUnitInfo ui)
		{
			UInt64 strLen;

			// parse str len 
			strLen = _chm_parse_cword(st);			

			// parse path 			
			if (_chm_parse_UTF8(st, strLen, ref ui.path)==0)
				return 0;

			// parse info 
			ui.space  = (CHMStream.CHM_COMPRESSION)_chm_parse_cword(st);
			ui.start  = _chm_parse_cword(st);
			ui.length = _chm_parse_cword(st);
			return 1;
		}

        /// <summary>
        /// Finds the object.
        /// </summary>
        /// <param name="st">The st.</param>
        /// <param name="block_len">The block_len.</param>
        /// <param name="objPath">The obj path.</param>
        /// <returns></returns>
		public IFileInfo FindObject(BinaryReader st, UInt32 block_len, string objPath)
		{	
			UInt32 end = (UInt32)st.BaseStream.Position+ block_len - free_space - _CHM_PMGL_LEN;			

			BaseStructure x=new BaseStructure();
			// now, scan progressively 
			chmUnitInfo FoundObject=new chmUnitInfo();

			string objPath2=x.NormallizeFileName(objPath);
			while (st.BaseStream.Position < end)
			{				
				_chm_parse_PMGL_entry(st,ref FoundObject);
				string xx=x.NormallizeFileName(FoundObject.path);
				if (xx==objPath2)
					return FoundObject;
			}
			FoundObject=null;

			return null;
		}
	}

    /// <summary>
    /// 
    /// </summary>
	public class chmPmgiHeader : BaseStructure
	{
        /// <summary>
        /// 
        /// </summary>
		public const int _CHM_PMGI_LEN=0x8;

        /// <summary>
        /// 
        /// </summary>
		public char[]	signature=null;           //  0 (PMGL) 
        /// <summary>
        /// 
        /// </summary>
		public UInt32	free_space=0;             //  4 			

        /// <summary>
        /// Read_pmgi_headers the specified st.
        /// </summary>
        /// <param name="st">The st.</param>
        /// <returns></returns>
		public int Read_pmgi_header(BinaryReader st)
		{
			signature=st.ReadChars(4);
		
			if ((signature[0]!='P') || (signature[1]!='M') || (signature[2]!='G') || (signature[3]!='I'))
				return 0;

			free_space=st.ReadUInt32();				
			return 1;
		}

        /// <summary>
        /// _chm_find_in_s the PMGI.
        /// </summary>
        /// <param name="st">The st.</param>
        /// <param name="block_len">The block_len.</param>
        /// <param name="objPath">The obj path.</param>
        /// <returns></returns>
		public Int32 _chm_find_in_PMGI(BinaryReader st, UInt32 block_len, string objPath)
		{		     			
			int page=-1;
			UInt64 strLen;
			string buffer="";
			uint end = (uint)st.BaseStream.Position + block_len - free_space - _CHM_PMGI_LEN;

			// now, scan progressively 
			while (st.BaseStream.Position < end)
			{
				// grab the name 
				strLen = _chm_parse_cword(st);
				buffer="";
				if (_chm_parse_UTF8(st, strLen, ref buffer)==0)
					return -1;

				// check if it is the right name 
				if (buffer.ToLower().CompareTo(objPath.ToLower())>0)
					return page;

				// load next value for path 
				page = (int)_chm_parse_cword(st);
			}
			return page;
		}
	}

    /// <summary>
    /// 
    /// </summary>
	public class chmPmgiEntry : BaseStructure
	{
        /// <summary>
        /// 
        /// </summary>
		public string	FileName="";
        /// <summary>
        /// 
        /// </summary>
		public ulong	NameLen=0;
        /// <summary>
        /// 
        /// </summary>
		public ulong	DirectoryChunk=0;

        /// <summary>
        /// Read_pmgi_entries the specified st.
        /// </summary>
        /// <param name="st">The st.</param>
        /// <returns></returns>
		public int Read_pmgi_entry(BinaryReader st)
		{
			NameLen = _chm_parse_cword(st);
			FileName="";
			if (_chm_parse_UTF8(st, NameLen, ref FileName)==0)
				return -1;
			DirectoryChunk= _chm_parse_cword(st);
			return 1;
		}		
	}

    /// <summary>
    /// 
    /// </summary>
	public class chmLzxcResetTable:BaseStructure
	{
        /// <summary>
        /// 
        /// </summary>
		public UInt32      version=0;
        /// <summary>
        /// 
        /// </summary>
		public UInt32      block_count=0;
        /// <summary>
        /// 
        /// </summary>
		public UInt32      unknown=0;
        /// <summary>
        /// 
        /// </summary>
		public UInt32      table_offset=0;
        /// <summary>
        /// 
        /// </summary>
		public UInt64      uncompressed_len=0;
        /// <summary>
        /// 
        /// </summary>
		public UInt64      compressed_len=0;
        /// <summary>
        /// 
        /// </summary>
		public UInt64      block_len=0;

        /// <summary>
        /// Read_lzxc_reset_tables the specified st.
        /// </summary>
        /// <param name="st">The st.</param>
        /// <returns></returns>
		public int Read_lzxc_reset_table(BinaryReader st)
		{
			version=st.ReadUInt32();  
			block_count=st.ReadUInt32();  
			unknown=st.ReadUInt32();  
			table_offset=st.ReadUInt32();  
			uncompressed_len=st.ReadUInt64();  
			compressed_len=st.ReadUInt64();  
			block_len=st.ReadUInt64();  
  				
			// check structure 
			if ((version == 2) || (version==3))
				return 1;
			else
				return 0;
		}
	}
	
	// structure of LZXC control data block 
    /// <summary>
    /// 
    /// </summary>
	public class chmLzxcControlData:BaseStructure
	{
        /// <summary>
        /// 
        /// </summary>
		public const int _CHM_LZXC_MIN_LEN=0x18;
        /// <summary>
        /// 
        /// </summary>
		public const int _CHM_LZXC_V2_LEN=0x1c;

        /// <summary>
        /// 
        /// </summary>
		public UInt32   size=0;                   //  0        
        /// <summary>
        /// 
        /// </summary>
		public char[]	signature=null;			  //  4 (LZXC) 
        /// <summary>
        /// 
        /// </summary>
		public UInt32   version=0;                //  8        
        /// <summary>
        /// 
        /// </summary>
		public UInt32   resetInterval=0;          //  c        
        /// <summary>
        /// 
        /// </summary>
		public UInt32   windowSize=0;             // 10        
        /// <summary>
        /// 
        /// </summary>
		public UInt32   windowsPerReset=0;        // 14        
        /// <summary>
        /// 
        /// </summary>
		public UInt32   unknown_18=0;             // 18        

        /// <summary>
        /// Read_lzxc_control_datas the specified st.
        /// </summary>
        /// <param name="st">The st.</param>
        /// <returns></returns>
		public int Read_lzxc_control_data(BinaryReader st)
		{
			size=st.ReadUInt32();
			signature=st.ReadChars(4);

			if (CheckSig("LZXC",signature)==false)
				return 0;

			version=st.ReadUInt32();
			resetInterval=st.ReadUInt32();
			windowSize=st.ReadUInt32();
			windowsPerReset=st.ReadUInt32();			

			if (size>=_CHM_LZXC_V2_LEN)
				unknown_18=st.ReadUInt32();
			else
				unknown_18 = 0;

			if (version == 2)
			{
				resetInterval *= 0x8000;
				windowSize *= 0x8000;				
			}
			if (windowSize == 0  ||  resetInterval == 0)
				return 0;

			// for now, only support resetInterval a multiple of windowSize/2 
			if (windowSize == 1)
				return 0;
			if ((resetInterval % (windowSize/2)) != 0)
				return 0;
	
			return 1;
		}
	}
	#endregion
}
